package com.zen.lon.open.remote.metadata;

import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;

import com.alibaba.fastjson.JSON;
import com.zen.lon.open.remote.exp.RemoteClientException;
import com.zen.lon.open.remote.support.ApplicationContextProvider;
import com.zen.lon.open.remote.utils.PathUtils;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;

/**
 * 方法调用元数据信息
 *
 * @author ZonLen since on 2021/7/9 下午10:22
 */
@Slf4j
public class RemoteClientMethodMetadata {

  private static final String IS_ENABLE_SIGN = "remote.client.enable.sign";

  private String url;

  private final RemoteClientMetadata remoteClientMetadata;

  private HttpMethod httpMethod;

  private final String interfacePath;

  private final Class<?> respClass;

  private final RemoteClientParamMetadata remoteClientParamMetadata;


  /**
   * 接口代理函数元数据信息
   */
  public RemoteClientMethodMetadata(Method method, RemoteClientMetadata remoteClientMetadata) {
    this.respClass = method.getReturnType();
    this.remoteClientMetadata = remoteClientMetadata;
    remoteClientParamMetadata = new RemoteClientParamMetadata(method);
    this.interfacePath = interfacePath(method);
  }

  /**
   * 接口路由
   */
  private String interfacePath(Method method) {
    final RequestMapping requestMapping = findMergedAnnotation(method, RequestMapping.class);
    if (null == requestMapping) {
      throw new RemoteClientException(
          "Both current class and supper class: method【" + method.getName()
              + "】has no mvc interface annotation as @RequestMapping...; please check");
    }
    final RequestMethod[] requestMethods = requestMapping.method();
    if (requestMethods.length == 0) {
      httpMethod = HttpMethod.GET;
    } else {
      httpMethod = HttpMethod.valueOf(requestMethods[0].name());
    }
    final String[] interfacePaths = requestMapping.value();
    if (interfacePaths.length == 0) {
      return "";
    }
    return PathUtils.primaryPath(interfacePaths);
  }

  /**
   * 创建RestTemplate
   */
  private RestTemplate restTemplate() {
    return remoteClientMetadata.restTemplate();
  }

  private String url() {
    if (StringUtils.isEmpty(url)) {
      this.url = remoteClientMetadata.baseUrl() + interfacePath;
    }
    return url;
  }

  /**
   * 执行请求
   */
  public Object execute(Object[] args) {
    final RestTemplate restTemplate = restTemplate();
    final HttpHeaders headers = headers();
    //开发、测试环境不签名
    if (Boolean.parseBoolean(
        ApplicationContextProvider.getProperty(IS_ENABLE_SIGN, Boolean.TRUE.toString()))) {
      remoteClientMetadata.signHeader(headers);
    }
    HttpEntity<Object> tHttpEntity = new HttpEntity<>(
        remoteClientParamMetadata.requestBodyParam(args), headers);
    final String remoteUrl = remoteClientMetadata
        .signUrl(remoteClientParamMetadata.requestParams(args, url()));
    log.info("Remote Client invoke API URL 【{}】; request method 【{}】; request param 【{}】",
        remoteUrl, httpMethod.toString(),
        Objects.nonNull(args) ? JSON.toJSONString(args) : "");
    final Object body = restTemplate
        .exchange(remoteUrl, httpMethod, tHttpEntity, ParameterizedTypeReference.forType(respClass),
            remoteClientParamMetadata.pathVariables(args)).getBody();
    log.info("Remote Client response json result 【{}】", JSON.toJSONString(body));
    return body;
  }

  private HttpHeaders headers(){
    final HttpHeaders headers = new HttpHeaders();
    headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    return headers;
  }
}
